//
// Copyright (C) 2002-2004 NVIDIA 
// 
// File: pluginMain.cpp
//
// Author: Jim Atkinson
//
// Changes:
//  10/2003  Kurt Harriman - www.octopusgraphics.com +1-415-893-1023
//           - "-pluginPath/pp" flag of cgfxShader command returns the
//             full path of the "cgfxShader" subdirectory beneath the
//             directory from which the plug-in binary was loaded.
//             Supporting files such as MEL scripts can be loaded from
//             this directory to avoid inadvertently picking up wrong
//             versions from random directories on the search path.
//           - The plug-in executes the cgfxShader_initUI.mel script
//             from this directory at the time the plug-in is loaded.
//           - The MEL command `pluginInfo -q -version cgfxShader` 
//             returns the plug-in version and cgfxShaderNode.cpp
//             compile date.
//  11/2003  Kurt Harriman - www.octopusgraphics.com +1-415-893-1023
//           - To load or reload a CgFX file, use the cgfxShader command
//             "-fx/fxFile <filename>" flag.  Setting the cgfxShader
//             node's "s/shader" attribute no longer loads the file.
//
//-
// ==========================================================================
// Copyright (C) 1995 - 2006 Autodesk, Inc. and/or its licensors.  All 
// rights reserved.
//
// The coded instructions, statements, computer programs, and/or related 
// material (collectively the "Data") in these files contain unpublished 
// information proprietary to Autodesk, Inc. ("Autodesk") and/or its 
// licensors, which is protected by U.S. and Canadian federal copyright 
// law and by international treaties.
//
// The Data is provided for use exclusively by You. You have the right 
// to use, modify, and incorporate this Data into other products for 
// purposes authorized by the Autodesk software license agreement, 
// without fee.
//
// The copyright notices in the Software and this entire statement, 
// including the above license grant, this restriction and the 
// following disclaimer, must be included in all copies of the 
// Software, in whole or in part, and all derivative works of 
// the Software, unless such copies or derivative works are solely 
// in the form of machine-executable object code generated by a 
// source language processor.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. 
// AUTODESK DOES NOT MAKE AND HEREBY DISCLAIMS ANY EXPRESS OR IMPLIED 
// WARRANTIES INCLUDING, BUT NOT LIMITED TO, THE WARRANTIES OF 
// NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR 
// PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE, OR 
// TRADE PRACTICE. IN NO EVENT WILL AUTODESK AND/OR ITS LICENSORS 
// BE LIABLE FOR ANY LOST REVENUES, DATA, OR PROFITS, OR SPECIAL, 
// DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES, EVEN IF AUTODESK 
// AND/OR ITS LICENSORS HAS BEEN ADVISED OF THE POSSIBILITY 
// OR PROBABILITY OF SUCH DAMAGES.
//
// ==========================================================================
//+
#include "cgfxShaderCommon.h"

#include "cgfxShaderNode.h"
#include "cgfxShaderCmd.h"
#include "cgfxVector.h"

#include <maya/MFnPlugin.h>
#include <maya/MIOStream.h>
#include <maya/MSceneMessage.h>
#include <maya/MNodeMessage.h>
#include <maya/MGlobal.h>
#include <maya/MFileIO.h>
#include <maya/MItDependencyNodes.h>
#include <maya/MDGMessage.h>
#if defined(_SWATCH_RENDERING_SUPPORTED_)
	#include <maya/MHWShaderSwatchGenerator.h>
	#include <maya/MHardwareRenderer.h>
#endif

// callbackIds is an array of callback identifiers which need to be
// cancelled when the plug-in is unloaded.
//
static MIntArray callbackIds;

static void cgfxShaderFileSaveCB(void* clientData );
static void cgfxShaderFileReadCB(void* clientData );

MStatus initializePlugin( MObject obj )
//
//	Description:
//		this method is called when the plug-in is loaded into Maya.  It 
//		registers all of the services that this plug-in provides with 
//		Maya.
//
//	Arguments:
//		obj - a handle to the plug-in object (use MFnPlugin to access it)
//
{ 
	MString   sVer = cgfxShaderNode::getPluginVersion();

#if defined(_SWATCH_RENDERING_SUPPORTED_)
    	// Allow an environment variable to override usage of swatch rendering.
	// Set the environment variable to a value other than 0 for it to take effect.
	const char *cgfxEnvVar = getenv("CGFX_SWATCH_RENDERING");

	MString UserClassify = MString( "shader/surface/utility" );

	// Don't initialize swatches in batch mode
	if (MGlobal::mayaState() != MGlobal::kBatch)
	{
		const MString& swatchName =	MHWShaderSwatchGenerator::initialize();

#ifdef _WIN32
	    if (!cgfxEnvVar)
			UserClassify = MString( "shader/surface/utility/:swatch/"+swatchName );
	    else
	    {
	        if ( 0 != strcmp(cgfxEnvVar,"0") )
				UserClassify = MString( "shader/surface/utility/:swatch/"+swatchName );
	    }
#else
	    if ( cgfxEnvVar && ( 0 != strcmp(cgfxEnvVar,"0") ) )
	    {
			UserClassify = MString( "shader/surface/utility/:swatch/"+swatchName );
	    }
#endif
	}

#else
	const MString UserClassify( "shader/surface/utility" );
#endif

	MFnPlugin plugin( obj, "NVIDIA", sVer.asChar(), MApiVersion );

	MGlobal::displayInfo( sVer );   // display banner

	// Register/initialize localized string resources
	CHECK_MSTATUS( plugin.registerUIStrings(NULL, 
				   "cgfxShaderPluginInitStrings" ));

	// Create Cg Context & register the Cg error callback

#if defined(_SWATCH_RENDERING_SUPPORTED_)
	// The following code is only used on Maya versions 7.0 and
    // later. 
	MStatus status = MStatus::kFailure;
#if !defined(LINUX)
	// Always initialize a context on Linux since it is not predictable as to when 
	// we may get a valid context. 
	if (MGlobal::mayaState() != MGlobal::kInteractive)
#endif
	{
		MHardwareRenderer *pRenderer = MHardwareRenderer::theRenderer();

		if (pRenderer) {
			const MString & backEndStr = pRenderer->backEndString();
			unsigned int width = 64, height = 64;
			status = pRenderer->makeSwatchContextCurrent( backEndStr,
				width, 
				height );
		}
		if (status != MStatus::kSuccess) {
			MGlobal::displayError(MString("Unqualified video card : Offscreen contexts not supported. CgFx plugin will not be loaded."));
			return MStatus::kFailure;
		}
	}
#else
#error "CgFx requires the Maya version 7 or greater"
#endif

	cgfxShaderNode::sCgContext = cgCreateContext();
	cgSetErrorCallback(cgfxShaderNode::cgErrorCallBack);
	cgSetErrorHandler(cgfxShaderNode::cgErrorHandler, 0);
	cgGLRegisterStates(cgfxShaderNode::sCgContext);
	cgGLSetManageTextureParameters(cgfxShaderNode::sCgContext, CG_TRUE);

	CHECK_MSTATUS( plugin.registerNode("cgfxShader",
		cgfxShaderNode::sId,
		cgfxShaderNode::creator,
		cgfxShaderNode::initialize,
		MPxNode::kHwShaderNode,
		&UserClassify));

	CHECK_MSTATUS( plugin.registerNode("cgfxVector",
		cgfxVector::sId,
		cgfxVector::creator,
		cgfxVector::initialize));

	CHECK_MSTATUS( plugin.registerCommand("cgfxShader",
		cgfxShaderCmd::creator,
		cgfxShaderCmd::newSyntax));

	// Where are my MEL scripts?
	cgfxShaderCmd::sPluginPath = plugin.loadPath();

	// Run MEL script for user interface initialization.
	if (MGlobal::mayaState() == MGlobal::kInteractive)
	{
		MString sCmd = "evalDeferred \"source \\\"cgfxShader_initUI.mel\\\"\"";
		MGlobal::executeCommand( sCmd );
	}

	// Skip the status checking on the addCallback calls since the only
	// way that they can really fail is if Maya is out of memory and then
	// everything is going to fall apart anyway.
	//
	MCallbackId id;
	id = MSceneMessage::addCallback(MSceneMessage::kAfterNew,
		cgfxShaderFileReadCB, NULL, &status);
	CHECK_MSTATUS(status);
	callbackIds.append((int)id);

	id = MSceneMessage::addCallback(MSceneMessage::kAfterImport,
		cgfxShaderFileReadCB, NULL, &status);
	CHECK_MSTATUS(status);
	callbackIds.append((int)id);

	id = MSceneMessage::addCallback(MSceneMessage::kAfterOpen,
		cgfxShaderFileReadCB, NULL, &status);
	CHECK_MSTATUS(status);
	callbackIds.append((int)id);

	id = MSceneMessage::addCallback(MSceneMessage::kAfterReference,
		cgfxShaderFileReadCB, NULL, &status);
	CHECK_MSTATUS(status);
	callbackIds.append((int)id);

	// call backs for "source directory" removal...
	id = MSceneMessage::addCallback(MSceneMessage::kBeforeSave,
		cgfxShaderFileSaveCB, NULL, &status);
	CHECK_MSTATUS(status);
	callbackIds.append((int)id);

	id = MSceneMessage::addCallback(MSceneMessage::kBeforeExport,
		cgfxShaderFileSaveCB, NULL, &status);
	CHECK_MSTATUS(status);
	callbackIds.append((int)id);

	return MStatus::kSuccess;
}

MStatus uninitializePlugin( MObject obj)
//
//	Description:
//		this method is called when the plug-in is unloaded from Maya. It 
//		deregisters all of the services that it was providing.
//
//	Arguments:
//		obj - a handle to the plug-in object (use MFnPlugin to access it)
//
{
	MStatus   status;
	MFnPlugin plugin( obj );

	cgDestroyContext(cgfxShaderNode::sCgContext);

	// Remove all the callbacks that we registered.
	//
	MMessage::removeCallbacks(callbackIds);

	// Deregister our node types.
	//
	CHECK_MSTATUS(plugin.deregisterNode( cgfxShaderNode::sId ));
	CHECK_MSTATUS(plugin.deregisterNode( cgfxVector::sId ));

	// Deregister our commands.
	//
	CHECK_MSTATUS(plugin.deregisterCommand( "cgfxShader" ));

	return MStatus::kSuccess;
}


static void cfxfFxFileChanged( MPlug& plug )
{
	// The easiest thing to do is to simply issue the
	// cgfxShader command to reload the shader.
	//
	MString fxFile;
	plug.getValue(fxFile);

	MString nodeName = plug.name();

	int index = nodeName.index('.');

	if (index >= 0)
	{
		nodeName = nodeName.substring(0, index - 1);

		MString cmd("cgfxShader -e -fx \"");
		cmd += fxFile;
		cmd += "\" ";
		cmd += nodeName;
		cmd += ";";

		MGlobal::executeCommand(cmd);
	}
}

static void cgfxShaderFileSaveCB(void* clientData )
{
	// Look through the scene for cgfxShader nodes whose effect is NULL
	// but whose shader attribute is not empty.
	//
	MStatus status;

	MString workspace;
	status = MGlobal::executeCommand(MString("workspace -q -rd;"),
		workspace);
	if (!status)
	{
		workspace.clear();
	}

	MItDependencyNodes nodeIt;

	for (nodeIt.reset(MFn::kPluginHwShaderNode);
		!nodeIt.isDone();
		nodeIt.next())
	{
		MObject oNode = nodeIt.item();

		MFnDependencyNode fnNode(oNode);
		if (fnNode.typeId() == cgfxShaderNode::sId)
		{
			// We've got a winner.
			//
			cgfxShaderNode* pNode = (cgfxShaderNode*)fnNode.userNode();
			MString ShaderFxFile = pNode->shaderFxFile();
			if (strncmp(ShaderFxFile.asChar(),workspace.asChar(),workspace.length()) == 0)
			{
				ShaderFxFile = ShaderFxFile.substring(workspace.length(),ShaderFxFile.length() - 1);
				MPlug plShader = fnNode.findPlug( pNode->sShader );
				plShader.setValue( ShaderFxFile );
				OutputDebugString("CGFX shader pathname saved as: ");
				OutputDebugString(ShaderFxFile.asChar());
				OutputDebugString("\n");
			}

			if( pNode->getTexturesByName())
			{
				cgfxAttrDefList::iterator it(pNode->attrDefList());

				while (it)
				{
					cgfxAttrDef* aDef = (*it);

					MObject oNode = pNode->thisMObject();

					switch (aDef->fType)
					{
					case cgfxAttrDef::kAttrTypeColor1DTexture:
					case cgfxAttrDef::kAttrTypeColor2DTexture:
					case cgfxAttrDef::kAttrTypeColor3DTexture:
					case cgfxAttrDef::kAttrTypeColor2DRectTexture:
					case cgfxAttrDef::kAttrTypeNormalTexture:
					case cgfxAttrDef::kAttrTypeBumpTexture:
					case cgfxAttrDef::kAttrTypeCubeTexture:
					case cgfxAttrDef::kAttrTypeEnvTexture:
					case cgfxAttrDef::kAttrTypeNormalizationTexture:
						{
							MString pathname;
							aDef->getValue(oNode, pathname);
							if (strncmp(pathname.asChar(),workspace.asChar(),workspace.length()) == 0)
							{
								pathname = pathname.substring(workspace.length(),pathname.length());
								aDef->setValue(oNode, pathname);
								OutputDebugString("CGFX texture pathname saved as: ");
								OutputDebugString(pathname.asChar());
								OutputDebugString("\n");
							}
						}
						break;
					default:
						break;
					}
					++it;
				}
			}
		}
	}
}

static void cgfxShaderFileReadCB(void* clientData ) 
{
	// Look through the scene for cgfxShader nodes whose effect is NULL
	// but whose shader attribute is not empty.
	//
	MStatus status;

	MItDependencyNodes nodeIt;

	for (nodeIt.reset(MFn::kPluginHwShaderNode);
		!nodeIt.isDone();
		nodeIt.next())
	{
		MObject oNode = nodeIt.item();

		MFnDependencyNode fnNode(oNode);
		if (fnNode.typeId() == cgfxShaderNode::sId)
		{
			// We've got a winner.
			//
			// cgfxShaderNode* pNode = (cgfxShaderNode*)fnNode.userNode();

			// And invoke the callback to reconnect everything
			//
			MPlug plug(oNode, cgfxShaderNode::sShader);
			cfxfFxFileChanged( plug );
		}
	}
}



